package br.univali.compiladores.compilador.semantico;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import br.univali.compiladores.compilador.Token;


public class AnalisadorSemantico {

	private static final String PUT_THIS = "put this";
	private static final String SHOW_THIS = "show this";
	private static final String ENTRADA_DADOS = "entrada dados";
	private static final String ATRIBUICAO = "atribuicao";
	private static final String AS_CONSTANT = "as constant";
	private static final String AS_VARIABLE = "as variable";
	private List<ItemTabelaDeSimbolos> tabelaDeSimbolos;
	private LinkedList<Integer> pilhaDesvios;
	private Hashtable<Integer, InstrucaoMaquinaVirtual> tabelaDeInstrucoes;
	public Hashtable<Integer, InstrucaoMaquinaVirtual> getTabelaDeInstrucoes() {
		return tabelaDeInstrucoes;
	}


	private List<ItemTupla> tupla;
	private int vt;
	private int vp;
	private int vi;
	private int tvi;
	private int ponteiro;
	private String contexto;
	private Map<Integer, List<Object>> tipoAtributoCategoria;
	private boolean variavelIndexada;
	private String saida;
	private int tipo;
	
	public AnalisadorSemantico() {
		tabelaDeInstrucoes = new Hashtable<Integer, InstrucaoMaquinaVirtual>();
		pilhaDesvios = new LinkedList<Integer>();
		tabelaDeSimbolos = new Stack<ItemTabelaDeSimbolos>();
		tipoAtributoCategoria = new Hashtable<Integer, List<Object>>();
		tupla = new ArrayList<ItemTupla>();
		ponteiro = 1;
		vt = 0;
		vp = 0;
		variavelIndexada = false;
	}
		
	
	
	public void ACAO_1(String identificador) {
		tabelaDeSimbolos.add(new ItemTabelaDeSimbolos(identificador, 0, "-", -1));
	}
	
	public void ACAO_2() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.STP, 0));
	}
	
	public void ACAO_3() {
		contexto = AS_CONSTANT;
		vi = 0;
		tvi = 0;
	}
	
	public void ACAO_4() {
		int indice = tabelaDeSimbolos.size() - 1;
		
		for (int i = vp + vi; i > 0; --i) {
			 ItemTabelaDeSimbolos item = tabelaDeSimbolos.get(indice);
			 item.setCategoria(tipo);
			--indice;
		}
		
		vp += tvi;
		
		if (tipo == 1 || tipo == 5) {
			tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.ALI, vp));
		} else if (tipo == 2 || tipo == 6) {
			tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.ALR, vp));
		} else if (tipo == 3 || tipo == 7) {
			tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.ALS, vp));
		} else if (tipo == 4) {
			tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.ALB, vp));
		}
		
		ponteiro++;
		
		if (tipo == 1 || tipo == 2 || tipo == 3 || tipo == 4) {
			vp  = 0;
			vi  = 0;
			tvi = 0;
		}
	}
	
	public void ACAO_5(Object valor) {
		switch(tipo) {
			case 5:
				tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDI, valor));
				break;
			case 6:
				tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDR, valor));
				break;
			case 7:
				tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDS, valor));
				break;
		}
		
		ponteiro++;
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.STC, vp));
		ponteiro++;
		vp = 0;
	}
	
	public void ACAO_6() {
		contexto = AS_VARIABLE;
	}
	
	public void ACAO_7(String identificador) {
		if (existeNaTabelaDeSimbolos(identificador)) {
			throw new RuntimeException("Identificador já declarado");
		} else {
			vt++;
			vp++;
			int valorDesconhecido = -1;
			int indice = -1;
			tabelaDeSimbolos.add(new ItemTabelaDeSimbolos(identificador, valorDesconhecido, vt, indice));
		}
	}
	
	public void ACAO_8(String identificador) {
		List<Object> lista = new ArrayList<Object>();
		
		if (contexto.equals(AS_VARIABLE)) {
			if (existeNaTabelaDeSimbolos(identificador)) {
				throw new RuntimeException("Identificador ja declarado: " + identificador);
			} else {
				variavelIndexada = false;
				lista.add(identificador);
				tipoAtributoCategoria.put(8, lista);
			}
		} else if (contexto.equals(ENTRADA_DADOS) || contexto.equals(ATRIBUICAO)) {
			variavelIndexada = false;
			lista.add(identificador);
			tipoAtributoCategoria.put(8, lista);
		}
	}
	
	public void ACAO_9() {
		List<Object> lista = new ArrayList<Object>();
		
		if (contexto.equals(AS_VARIABLE)) {
			if (!variavelIndexada) {
				vt++;
				vp++;
				tabelaDeSimbolos.add(new ItemTabelaDeSimbolos((String) tipoAtributoCategoria.get(8).get(0), -1, vt, -1));
			} else {
				vi++;
				tvi += (Integer) tipoAtributoCategoria.get(10).get(0);
				tabelaDeSimbolos.add(new ItemTabelaDeSimbolos((String) tipoAtributoCategoria.get(8).get(0), -1, vt + 1, (Integer) tipoAtributoCategoria.get(10).get(0)));
				vt += (Integer) tipoAtributoCategoria.get(10).get(0);
			}
		} else if (contexto.equals(ATRIBUICAO)) {
			String identificador = (String) tipoAtributoCategoria.get(8).get(0);
			
			if (existeNaTabelaDeSimbolos(identificador) && ehIdentificadorDeVariavel(identificador)) {
				ItemTabelaDeSimbolos itemTabelaDeSimbolos = obterItemTabelaDeSimbolosPeloIdentificador(identificador);
				Object atributo1 = itemTabelaDeSimbolos.getAtributo();
				int atributo2 = itemTabelaDeSimbolos.getIndice();
				
				if (atributo2 == -1) {
					if (!variavelIndexada) {
						lista.add(atributo1);
						tipoAtributoCategoria.put(9, lista);
					} else {
						throw new RuntimeException("Identificador de variavel nao indexada.");
					}
				} else {
					if (variavelIndexada) {
						int constanteInteira = (int) tipoAtributoCategoria.get(10).get(0);
						
						lista.add((Integer) atributo1 + constanteInteira - 1);
						tipoAtributoCategoria.put(9, lista);
					} else {
						throw new RuntimeException("identificador de variavel indexada exige indice");
					}
				}
			} else {
				throw new RuntimeException("identificador não declarado ou identificador de programa ou de constante");
			}
		} else if (contexto.equals(ENTRADA_DADOS)) {
			String identificador = (String) tipoAtributoCategoria.get(8).get(0);
			
			if (existeNaTabelaDeSimbolos(identificador) && ehIdentificadorDeVariavel(identificador)) {
				ItemTabelaDeSimbolos itemTabelaDeSimbolos = obterItemTabelaDeSimbolosPeloIdentificador(identificador);
				Object atributo1 = itemTabelaDeSimbolos.getAtributo();
				int atributo2 = itemTabelaDeSimbolos.getIndice();
				
				if (atributo2 == -1){
					if (!variavelIndexada) {
						int categoria = itemTabelaDeSimbolos.getCategoria();
						tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.REA, categoria));
						ponteiro++;
						tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.STR, atributo1));
						ponteiro++;
					} else {
						throw new RuntimeException("Identificador de variavel nao indexada.");
					}
				} else {
					if (variavelIndexada) {
						int categoria = itemTabelaDeSimbolos.getCategoria();
						tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.REA, categoria));
						ponteiro++;
						int constanteInteira = (int) tipoAtributoCategoria.get(10).get(0);
						tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.STR, (Integer) atributo1 + constanteInteira - 1));
						ponteiro++;
					} else {
						throw new RuntimeException("identificador de variável indexada exige índice");
					}
				}
			} else {
				throw new RuntimeException("identificador não declarado ou identificador de programa ou de constante");
			}
		}
	}
	
	public void ACAO_10(int constanteInteira) {
		List<Object> lista = new ArrayList<Object>();
		
		lista.add(constanteInteira);
		tipoAtributoCategoria.put(10, lista);
		variavelIndexada = true;
	}
	
	public void ACAO_11() {
		if (contexto.equals(AS_VARIABLE)) {
			tipo = 1;
		} else {
			tipo = 5;
		}
	}
	
	public void ACAO_12() {
		if (contexto.equals(AS_VARIABLE)) {
			tipo = 2;
		} else {
			tipo = 6;
		}
	}
	
	public void ACAO_13() {
		if (contexto.equals(AS_VARIABLE)) {
			tipo = 3;
		} else {
			tipo = 7;
		}
	}
	
	public void ACAO_14() {
		if (contexto.equals(AS_VARIABLE)) {
			tipo = 3;
		} else {
			throw new RuntimeException("Tipo invalido para constante: BOOLEAN");
		}
	}
	
	public void ACAO_15() {
		contexto = ATRIBUICAO;
	}
	
	public void ACAO_16() {
		List<Object> lista = tipoAtributoCategoria.get(9);
		
		for (Object item : lista) {
			tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.STR, item));
			ponteiro++;
		}
	}
	
	public void ACAO_17() {
		contexto = ENTRADA_DADOS;
	}
	
	public void ACAO_18() {
		saida = SHOW_THIS;
	}
	
	public void ACAO_19() {
		saida = PUT_THIS;
	}
	
	public void ACAO_20() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.WRT, 0));
		ponteiro++;
	}
	
	public void ACAO_21(String identificador) {
		if (existeNaTabelaDeSimbolos(identificador) && ehIdentificadorDeVariavelOuConstante(identificador)) {
			variavelIndexada = false;
			List<Object> lista = new ArrayList<Object>();
			lista.add(identificador);
			tipoAtributoCategoria.put(21, lista);
		} else {
			throw new RuntimeException("identificador não declarado ou identificador de programa");
		}
	}
	
	public void ACAO_22(Token t) {
		
		String identificador = (String) tipoAtributoCategoria.get(21).get(0); 

		ItemTabelaDeSimbolos item = obterItemTabelaDeSimbolosPeloIdentificador(identificador);
		Object atributo1 = item.getAtributo();
		int atributo2 = item.getIndice();
		
		if (!variavelIndexada)
			if (atributo2 == -1) {
				if (saida.equals(SHOW_THIS)) {
					tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDS, t.image + "="));
					ponteiro++;
					tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.WRT, 0));
					ponteiro++;
				}
				
				tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDV, atributo1));
				ponteiro++;
			} else
				throw new RuntimeException("identificador de variável indexada exige índice");
		else
			if (atributo2 != -1) {
				if (saida.equals(SHOW_THIS)) {
					tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDS, t.image + "="));
					ponteiro++;
					tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.WRT, 0));
					ponteiro++;
				}
				
				int constanteInteira = (int) tipoAtributoCategoria.get(10).get(0);
				tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDV, ((Integer)atributo1) + (constanteInteira - 1)));
				ponteiro++;
			} else {
				throw new RuntimeException("identificador de constante ou de variável não indexada");
			}
	}
	
	public void ACAO_23(int constanteInteira) {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDI, constanteInteira));
		ponteiro++;
	}
	
	public void ACAO_24(float constanteReal) {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDR, constanteReal));
		ponteiro++;
	}
	
	public void ACAO_25(String constanteLiteral) {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDS, constanteLiteral));
		ponteiro++;
	}
	
	public void ACAO_26() {
		InstrucaoMaquinaVirtual instrucaoMaquinaVirtual = tabelaDeInstrucoes.get(pilhaDesvios.pop());
		instrucaoMaquinaVirtual.setParametro(ponteiro);
	}// OK
	
	public void ACAO_27() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.JMF, "?"));
		pilhaDesvios.push(ponteiro);
		ponteiro++;
	}// OK
	
	public void ACAO_28() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.JMT, "?"));
		pilhaDesvios.push(ponteiro);
		ponteiro++;
	}// OK
	
	public void ACAO_29() {
		InstrucaoMaquinaVirtual instrucaoMaquinaVirtual = tabelaDeInstrucoes.get(pilhaDesvios.pop());
		instrucaoMaquinaVirtual.setParametro(ponteiro + 1);
		
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.JMP, "?"));
		pilhaDesvios.push(ponteiro );
		ponteiro++;
	}// OK
	
	public void ACAO_30() {
		pilhaDesvios.push(ponteiro);
	}// OK
	
	public void ACAO_31() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.JMF, "?"));
		pilhaDesvios.push(ponteiro);
		ponteiro++;
	}// OK
	
	public void ACAO_32() {
		
		InstrucaoMaquinaVirtual instrucaoMaquinaVirtual = 
				tabelaDeInstrucoes.get(pilhaDesvios.pop());
		
		instrucaoMaquinaVirtual.setParametro(ponteiro + 1);
		
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.JMP, pilhaDesvios.pop()));
		ponteiro++;
	}// OK
	
	public void ACAO_33() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.EQL, 0));
		ponteiro++;
	}
	
	public void ACAO_34() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.DIF, 0));
		ponteiro++;
	}
	
	public void ACAO_35() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.SMR, 0));
		ponteiro++;
	}
	
	public void ACAO_36() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.BGR, 0));
		ponteiro++;
	}
	
	public void ACAO_37() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.SME, 0));
		ponteiro++;
	}
	
	public void ACAO_38() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.BGE, 0));
		ponteiro++;
	}
	
	public void ACAO_39() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.ADD, 0));
		ponteiro++;
	}
	
	public void ACAO_40() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.SUB, 0));
		ponteiro++;
	}
	
	public void ACAO_41() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.OR, 0));
		ponteiro++;
	}
	
	public void ACAO_42() {
		//-*
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.MUL, 0));
		ponteiro++;
	}// OK
	
	public void ACAO_43() {
		//-/
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.DIV, 0));
		ponteiro++;
	}// OK
	
	public void ACAO_44() {
		//%
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.DVI, 0));
		ponteiro++;
	}// OK
	
	public void ACAO_45() {
		//%%
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.MOD, 0));
		ponteiro++;
	}// OK
	
	public void ACAO_46() {
		//&
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.AND, 0));
		ponteiro++;
	}// OK
	
	public void ACAO_47() {
		//**
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.POW, 0));
		ponteiro++;
	}// OK
	
	public void ACAO_48() {
		List<Object> lista = tipoAtributoCategoria.get(21);
		String identificador = (String) lista.get(0);

		ItemTabelaDeSimbolos item = obterItemTabelaDeSimbolosPeloIdentificador(identificador);
		Object atributo1 = item.getAtributo();
		int atributo2 = item.getIndice();
		
		if (!variavelIndexada)
			if (atributo2 == -1) {
				tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDV, atributo1));
				ponteiro++;
			} else
				throw new RuntimeException("identificador de variável indexada exige índice");
		else if (atributo2 != -1) {
			int constanteInteira = (int) tipoAtributoCategoria.get(10).get(0);
			tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDV, ((Integer)atributo1) + (constanteInteira - 1)));
			ponteiro++;
		} else
			throw new RuntimeException("identificador de constante ou de variável não indexada");
	}
	
	public void ACAO_49() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDB, true));
		ponteiro++;
	}
	
	public void ACAO_50() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.LDB, false));
		ponteiro++;
	}
	
	public void ACAO_51() {
		tabelaDeInstrucoes.put(ponteiro, new InstrucaoMaquinaVirtual(CodigoInstrucao.NOT, 0));
		ponteiro++;
	}
	
	private boolean existeNaTabelaDeSimbolos(String identificador) {
		for (ItemTabelaDeSimbolos item : tabelaDeSimbolos) {
			if (item.getNome().equals(identificador)) return true;
		}
		
		return false;
	}
	
	private boolean ehIdentificadorDeVariavel(String identificador) {
		for (ItemTabelaDeSimbolos item : tabelaDeSimbolos) {
			if (item.getNome().equals(identificador)) {
				return item.getCategoria() < 5;
			}
		}
		
		return false;
	}
	
	private boolean ehIdentificadorDeVariavelOuConstante(String identificador) {
		for (ItemTabelaDeSimbolos item : tabelaDeSimbolos) {
			if (item.getNome().equals(identificador)) {
				return item.getCategoria() < 8;
			}
		}
		
		return false;
	}
	
	private ItemTabelaDeSimbolos obterItemTabelaDeSimbolosPeloIdentificador(String identificador) {
		for (ItemTabelaDeSimbolos item : tabelaDeSimbolos) {
			if (item.getNome().equals(identificador)) {
				return item;
			}
		}
		
		throw new InvalidParameterException("Identificador nao consta na lista de tabela de simbolos");
	}
}